/**
 * ...
 * @author Franco Ponticelli
 */

package uform.model.cell;
import uform.model.core.Crawler;
import uform.model.cell.Cell;
import thx.util.Result;
import uform.model.value.ValueType;
import Types;
import thx.error.Error;
using thx.util.Results;
using Arrays;
using StringTools;

class ArrayCell<T> extends Cell<T, Array<T>>
{
	static var _reNull  = ~/^\s*null\s*/m;
	static var _reOpen  = ~/^\s*\[\s*/m;
	static var _reClose = ~/^\s*\]\s*/m;
	static var _reNext  = ~/^\s*,\s*/m;
	
	public var min : Int;
	public var max : Null<Int>;
	public function new(type : ValueType<T>, min : Int = 0, ?max : Null<Int>)
	{
		super(type);
		if (null == min || min < 0)
			throw "invalid negative min value (" + min + ")";
		if (max == 0)
			throw "max cannot be 0";
		if (null != max && max < min)
			throw "invalid max (" + max + ") value lower than min (" + min + ")";
		this.min = min;
		this.max = max;
	}
	
	override function isOptional()
	{
		return min == 0;
	}
	
	override function validate(value : Dynamic) : Result
	{
		if (null == value)
		{
			if (isOptional())
			{
				return Result.Ok;
			} else {
				return "null value not allowed".failure();
			}
		} else if (Std.is(value, Array)) {
			var a : Array<Dynamic> = cast value;
			if (a.length < min)
			{
				return "collection has less value ({0}) than expected ({1})".failure([a.length, min]);
			} else if (null != max && a.length > max)
			{
				return "collection has more value ({0}) than expected ({1})".failure([a.length, max]);
			} else {
				for (i in 0...a.length)
				{
					var v = a[i];
					if (null == v)
						return "null value not allowed in collection at position {0}".failure(i);
					switch(_validateOne(v))
					{
						case Ok:
							continue;
						case Failure(e):
							return Result.Failure(e.add(new Error("at position {0}", i)));
					}
				}
			}
			return Result.Ok;
		} else {
			return "expected an array but value is '{0}')".failure(Types.typeName(value));
		}
	}
	
	function _validateOne(value : Dynamic)
	{
		var r = type.validateType(value);
		switch(r)
		{
			case Ok:
				return type.validate(value);
			default:
				return r;
		}
	}
	
	override function parse(s : String, ?ref : { s : String }) : Array<T>
	{
		if (null == s || "" == s.trim())
			return null;
		if (null == ref)
			ref = { s : s };
		if (_reNull.match(ref.s))
		{
			ref.s = _reNull.matchedRight();
			return null;
		}
		
		if (!_reOpen.match(ref.s))
			throw new Error("expected array opening '[' but found '{0:T,20}'", ref.s);
		ref.s = _reOpen.matchedRight();
		
		var result = [];
		
		var next = !_reClose.match(ref.s);
		while (next)
		{
			result.push(type.parse(ref.s, ref));
			if (next = _reNext.match(ref.s))
				ref.s = _reNext.matchedRight();
		}
		
		if (!_reClose.match(ref.s))
			throw new Error("expected array closing ']' but found '{0:T,20}'", ref.s);
		ref.s = _reClose.matchedRight();
			
		return result;
	}
	
	override function render(value : Array<T>) : String
	{
		var buf = [];
		for (v in value)
			buf.push(type.render(v));
		return "[" + buf.join(",") + "]";
	}
	
	override function toString()
	{
		return "ArrayCell { type: " + type + ", min: " + min + ", max: " + max + " }";
	}
	
	static var _rePos = ~/^\[(\d+)\]$/;
	
	override public function resolve(crawler : Crawler, param : String)
	{
		if (param == '')
		{
			if(crawler.hasNext()) {
				throw new Error("the path should point at a position");
			} else {
				return;
			}
		}
		if (!_rePos.match(param))
			throw new Error("invalid position '{0}'", param);
			
		var pos = Std.parseInt(_rePos.matched(1));
		var pget = crawler.get;
		crawler.get = function()
		{
			var t : Dynamic = pget();
			if (null == t)
				return null;
			else
				return t[pos];
		};
		
		var pset = crawler.set;
		crawler.set = function(v)
		{
			var t : Dynamic = pget();
			if (null == t)
			{
				t = [];
				pset(t);
			}
			t[pos] = v;
		};
		
		crawler.handler = type;
	}
}